%%%-------------------------------------------------------------------
%%% @author chenlong
%%% @copyright (C) 2019, <COMPANY>
%%% @doc
%%% 房间管理系统
%%% @end
%%% Created : 21. 三月 2019 11:50
%%%-------------------------------------------------------------------
-module(room_manager).
-author("chenlong").

-behaviour(gen_server).

-include("common.hrl").
-include_lib("stdlib/include/ms_transform.hrl").

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1,
	handle_call/3,
	handle_cast/2,
	handle_info/2,
	terminate/2,
	code_change/3]).

-define(SERVER, ?MODULE).
-define(HEART_BEAT_TIME, 5).%%进程心跳60s

-record(state, {}).

-export([createRoom/1,getRoomList/1, getEtsRoom/1,getEtsBattle/1, getEtsRoomListBySetting/1]).


%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
	{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
	gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
	{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term()} | ignore).
init([]) ->
	ets:new(?ETS_ROOM, [set, named_table, public, {keypos,#ets_room.roomID}, ?ETS_CONCURRENCY]),
	ets:new(?ETS_BATTLE, [set, named_table, public, {keypos,#ets_battle.roomID}, ?ETS_CONCURRENCY]),
	process_flag(trap_exit, true),
	%% 开始时间轮
	timer_wheel:init(),
	Now = util:now(),
	timer_wheel:plan(Now+?HEART_BEAT_TIME, fun heartBeat/1),
	{ok, #state{}}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
	State :: #state{}) ->
	{reply, Reply :: term(), NewState :: #state{}} |
	{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
	{noreply, NewState :: #state{}} |
	{noreply, NewState :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
	{stop, Reason :: term(), NewState :: #state{}}).
handle_call(Request, _From, State) ->
	R =
	try
		case Request of
			_ ->
				?ERR("no match module=~p, Request=~p", [?MODULE, Request]),
				error
		end
	catch
		_:Why:StackTrace ->
			?ERR("room_manager handle_call Request=~p,Why=~p,StackTrace=~p", [Request, Why,StackTrace]),
			error
	end,
	{reply, R, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #state{}) ->
	{noreply, NewState :: #state{}} |
	{noreply, NewState :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State) ->
	{noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
	{noreply, NewState :: #state{}} |
	{noreply, NewState :: #state{}, timeout() | hibernate} |
	{stop, Reason :: term(), NewState :: #state{}}).
handle_info(Info, State) ->
	try
		case Info of
			{createRoom, MasterID, PID, Params} ->
				doCreateRoom(MasterID, PID, Params);
			{updateRoom, RoomID, ChangeList} ->
				doUpdateRoom(RoomID,ChangeList);
			{roomDestroy, RoomID} ->
				onRoomDestroy(RoomID);
			{?timer_wheel_tick, Sec} ->
				timer_wheel:work(Sec);
			{createFakeRoom, Num} ->
				doCreateFakeRoom(Num);
			_ ->
				?ERR("no match module=~p, Info=~p", [?MODULE, Info])
		end
	catch
		_:Why:StackTrace ->
			?ERR("room_manager handle_info Info=~p,Why=~p,StackTrace=~p", [Info, Why,StackTrace])
	end,
	{noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
	State :: #state{}) -> term()).
terminate(_Reason, _State) ->
	ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
	Extra :: term()) ->
	{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
	{ok, State}.

%%%===================================================================
%%% API
%%%===================================================================
createRoom({RoomName,RoomSec,SettingList}) ->
	room_manager ! {createRoom, role_data:getRoleID(), self(), {RoomName,RoomSec,SettingList}}.

%%return RoomList
getRoomList(0) ->
	RoomList = ets:tab2list(?ETS_ROOM),
	RoomList;
getRoomList(RoomID) ->
	ets:lookup(?ETS_ROOM,RoomID).

getEtsRoom(RoomID) ->
	case ets:lookup(?ETS_ROOM, RoomID) of
		[EtsRoom] -> EtsRoom;
		_ -> none
	end.

getEtsBattle(RoomID) ->
	case ets:lookup(?ETS_BATTLE,RoomID) of
		[EtsBattle] -> EtsBattle;
		_ -> none
	end.

%%通过游戏规则，筛选出符合的没有密码的房间列表
getEtsRoomListBySetting(SettingList) ->
	MaxMemberCount = battle_lib:getMaxRoomPlayerCount(),
	MS = ets:fun2ms(fun(#ets_room{roomSec = RoomSec,playerList = MemberList,roomState = RoomState}=EtsRoom)
		when RoomSec=="" andalso length(MemberList)<MaxMemberCount andalso RoomState==?ROOM_STATE_IDLE ->
		EtsRoom
	                end ),
	SelectedRoomList = ets:select(?ETS_ROOM,MS),
	FilterFun = fun(#ets_room{settingList = RoomSettingList}) ->
		RoomSettingList == SettingList
		end,
	FilterRoomList = lists:filter(FilterFun,SelectedRoomList),
	FilterRoomList.

%%%===================================================================
%%% Internal functions
%%%===================================================================
doCreateRoom(MasterID, PID, {RoomName,RoomSec,SettingList}) ->
	RoomID = tk_id:gen_roomID(),
	{ok, RoomPID}=supervisor:start_child(room_sup, [{RoomID, MasterID,SettingList}]),
	InitEtsRoom = #ets_room{
		roomID = RoomID,
		masterID = MasterID,
		roomPID = RoomPID,
		roomState = ?ROOM_STATE_IDLE,
		roomName = RoomName,
		roomSec = RoomSec,
		settingList = SettingList,
		playerList = [#room_player{playerID = MasterID,pos = 1}]
	},
	ets:insert(?ETS_ROOM, InitEtsRoom),
	PID ! {resCreateRoom, RoomID}.

doUpdateRoom(RoomID, ChangeList) ->
	ets:update_element(?ETS_ROOM, RoomID, ChangeList),
	ok.

onRoomDestroy(RoomID) ->
	ets:delete(?ETS_ROOM,RoomID),
	ets:delete(?ETS_BATTLE,RoomID),
	ok.

heartBeat(Sec) ->
	timer_wheel:plan(Sec+?HEART_BEAT_TIME, fun heartBeat/1),
	checkFakeRoom(Sec),
	ok.

%%===============================FAKE ROOM======================================
checkFakeRoom(Sec) ->
	%%是否没有假房间
	CurFakeCount = ets:select_count(?ETS_ROOM, ets:fun2ms(fun(#ets_room{isFake = IsFake})when IsFake -> ?TRUE end)),
	case CurFakeCount =< 0 of
		?TRUE ->
			%%没有则随机房间个数，创建
			Count = randomFakeRoomCount(),
			?MODULE ! {createFakeRoom, Count};
		_ ->
			%%是否到整点
			case Sec rem ?ONE_HOUR_SECONDS == 0 of
				?TRUE ->
					%%到整点，则随机房间个数，然后创建
					Count = randomFakeRoomCount(),
					case Count > CurFakeCount of
						?TRUE ->
							?MODULE ! {createFakeRoom, Count - CurFakeCount};
						_ -> ok
					end;
				_ -> ok
			end,
			%%是否有到期房间需要销毁
			DelRoomIDList = ets:select(?ETS_ROOM, ets:fun2ms(fun(#ets_room{isFake = IsFake, endTimestamp = EndTime, roomID = RoomID})when IsFake andalso Sec >= EndTime -> RoomID end)),
			lists:foreach(fun(RoomID) -> onRoomDestroy(RoomID) end,DelRoomIDList),
			ok
	end,
	ok.

randomFakeRoomCount() ->
	{Min, Max}=data_common:get(fake_room_num),
	util:random_int(Min, Max).
randomRoomName() ->
	NameList = data_room_name:get(name),
	util:random_one_from_list(NameList).
randomRoomSetting() ->
	util:for_map(1,?SETTING_MAX_LEN, fun(_I) -> util:random_int(0,1) end).
randomRoleName() ->
	NameList = data_name:get(name),
	util:random_one_from_list(NameList).
randomRoleHead() ->
	NameList = data_head:get(head),
	util:random_one_from_list(NameList).
randomPlayerList() ->
	{Min,Max}=data_common:get(fake_room_watching_num),
	WatchNum = util:random_int(Min, Max),
	util:for_map(1,2+WatchNum,fun(I) ->
		#room_player{
			playerID = {randomRoleName(),randomRoleHead()},
			pos = I,
			state = ?PLAYER_STATE_READY
		}
	                         end).
randomEndTimestamp(Now) ->
	{Min,Max}=data_common:get(fake_room_intenal_time),
	InternalTime = util:random_int(Min, Max),
	InternalTime + Now.

%%Num 告诉需要创建几个
doCreateFakeRoom(Num) ->
	Now = util:now(),
	CreateFun = fun(_Index) ->
		RoomID = tk_id:gen_roomID(),
		#ets_room{
			roomID = RoomID,
			masterID = 0,
			roomPID = 0,
			roomState = ?ROOM_STATE_BATTLE,
			roomName = randomRoomName(),
			roomSec = "fakeRoom",
			settingList = randomRoomSetting(),
			playerList = randomPlayerList(),
			isFake = ?TRUE,
			endTimestamp = randomEndTimestamp(Now)
		}
	            end,
	FakeRoomList = util:for_map(1, Num, CreateFun),
	ets:insert(?ETS_ROOM, FakeRoomList),
	ok.

%%===============================FAKE ROOM END======================================